package com.sailing.xjpb.log.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.sailing.xjpb.common.JsonUtils;
import com.sailing.xjpb.common.SailingConstants;
import com.sailing.xjpb.ipaas.client.core.IpaasHttpClient;
import com.sailing.xjpb.ipaas.client.core.UserContext;
import com.sailing.xjpb.log.config.SystemLogConfigProperties;
import com.sailing.xjpb.log.entity.bo.BusinessLoggerBO;
import com.sailing.xjpb.log.entity.dto.LogRecord;
import com.sailing.xjpb.log.service.BusinessLoggerService;
import com.sailing.xjpb.log.util.OperateEnum;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author sutao
 * createDate 2022/9/7 22:33
 */
@Slf4j
@Data
@SuppressWarnings({"rawtypes", "unchecked"})
public class BusinessLoggerServiceImpl implements BusinessLoggerService {

    static final String SUCCESS = "1";
    static final String FAIL = "0";
    static final String SUCCESS_CODE = "1000";
    static final int MAX_RETRIES = 3;

    @Autowired
    IpaasHttpClient ipaasHttpClient;

    @Autowired
    private SystemLogConfigProperties xtSystemLogConfig;

    private final Executor pools;

    public BusinessLoggerServiceImpl(ThreadPoolExecutor executor) {
        this.pools = executor;
    }

    @Override
    public void log(BusinessLoggerBO businessLoggerBO, Throwable throwable) {
        CompletableFuture.runAsync(() -> {
            try {
                String token = businessLoggerBO.getToken();
                UserContext.setAuthorization(token);
                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
                LogRecord logRecord = new LogRecord();
                logRecord.setOperateCondition(getOperateCondition(businessLoggerBO));
                logRecord.setOperateModule(businessLoggerBO.getClassDoc());
                logRecord.setOperateFunction(businessLoggerBO.getMethodDoc());
                setOperate(businessLoggerBO, logRecord);
                setResultState(businessLoggerBO, logRecord);
                logRecord.setOperateTime(sdf.format(new Date()));
                logRecord.setRequestIp(businessLoggerBO.getIpAddress());
                logRecord.setUri(businessLoggerBO.getUri());
                logRecord.setOrigin(businessLoggerBO.getUrl());
                //如果维护信息未空则默认未空
                logRecord.setSystemId(businessLoggerBO.getSystemId());
                Optional.ofNullable(businessLoggerBO.getSipUserInfoDTO()).ifPresent(u -> {
                    logRecord.setUserId(u.getUserid());
                    logRecord.setOrgId(u.getOrgId());
                    logRecord.setLoginName(u.getUsername());
                });
                sendLogMessage(logRecord, token, MAX_RETRIES);
            } finally {
                UserContext.clean();
            }
        }, pools);
    }

    private void sendLogMessage(LogRecord logRecord, String token, int retryCount) {
        retryCount--;
        int sendCount = this.getSendCount(retryCount);
        try {
            String logSaveUri = xtSystemLogConfig.getUri();
            JsonNode logResponse = ipaasHttpClient.exchange(logSaveUri, HttpMethod.POST, token, JsonNode.class,
                    new HashMap<>(1), logRecord);
            JsonNode status = logResponse.get("status");
            if (status == null || SailingConstants.isFalse(status.asText())) {
                log.error("第{}次发送操作日志到ipaass失败！ipaas接口返回值：{}, 记录请求uri：{}，发送参数值：{}"
                        , sendCount, logResponse, logSaveUri, logRecord);
                retry(logRecord, token, retryCount);
            }
            log.info("记录请求uri={}日志......发送成功，token={}", logRecord.getUri(), token);
        } catch (Exception var3) {
            String message = var3.getMessage();
            if (StringUtils.contains(message, "ConcurrentModificationException")) {
                // 解决星图V2.0图谱拖入画布时，频繁输出错误日志
                log.error("第{}次发送操作日志到ipaass失败！异常消息：{}", sendCount, message);
            } else {
                log.error("第{}次发送操作日志到ipaass失败！日志参数：{}", sendCount, logRecord, var3);
            }
            retry(logRecord, token, retryCount);
        }
    }

    private String getOperateCondition(BusinessLoggerBO businessLoggerBO) {
        Map<String, Object> queryParam = businessLoggerBO.getQueryParam();
        Object requestBody = businessLoggerBO.getRequestBody();
        if (MapUtils.isEmpty(queryParam) && requestBody != null) {
            return JsonUtils.toJSONString(requestBody);
        } else if (MapUtils.isNotEmpty(queryParam) && requestBody != null) {
            queryParam.put("requestBody", requestBody);
        }
        return JsonUtils.toJSONString(queryParam);
    }

    private void setOperate(BusinessLoggerBO businessLoggerBO, LogRecord logRecord) {
        if (businessLoggerBO.getOperateEnum() == null) {
            if (HttpMethod.POST.equals(businessLoggerBO.getMethod())) {
                logRecord.setOperateName(OperateEnum.INSERT.getOperateName());
                logRecord.setOperateType(OperateEnum.INSERT.getOperateType());
            } else if (HttpMethod.GET.equals(businessLoggerBO.getMethod())) {
                logRecord.setOperateName(OperateEnum.QUERY.getOperateName());
                logRecord.setOperateType(OperateEnum.QUERY.getOperateType());
            } else if (HttpMethod.PUT.equals(businessLoggerBO.getMethod())) {
                logRecord.setOperateName(OperateEnum.UPDATE.getOperateName());
                logRecord.setOperateType(OperateEnum.UPDATE.getOperateType());
            } else if (HttpMethod.DELETE.equals(businessLoggerBO.getMethod())) {
                logRecord.setOperateName(OperateEnum.DELETE.getOperateName());
                logRecord.setOperateType(OperateEnum.DELETE.getOperateType());
            } else {
                logRecord.setOperateName(OperateEnum.OTHER.getOperateName());
                logRecord.setOperateType(OperateEnum.OTHER.getOperateType());
            }
        } else {
            logRecord.setOperateName(businessLoggerBO.getOperateEnum().getOperateName());
            logRecord.setOperateType(businessLoggerBO.getOperateEnum().getOperateType());
        }

    }

    private void setResultState(BusinessLoggerBO businessLoggerBO, LogRecord logRecord) {
        if (SUCCESS_CODE.equals(businessLoggerBO.getResponseCode())) {
            logRecord.setOperateResult(SUCCESS);
        } else {
            logRecord.setOperateResult(FAIL);
            logRecord.setErrorCode(businessLoggerBO.getResponseCode());
        }
        Object responseBody = businessLoggerBO.getResponseBody();
        if (responseBody instanceof Map) {
            Map<String, Object> responseData = new HashMap<>((Map) responseBody);
            logRecord.setReturnResult(responseData);
        } else {
            logRecord.setReturnResult(responseBody);
        }
    }

    private void retry(LogRecord logRecord, String token, int retryCount) {
        if (retryCount < 0) {
            log.error("重试{}次后，发送操作日志到ipaass失败！，日志参数：{}", MAX_RETRIES, logRecord);
            return;
        }
        try {
            TimeUnit.SECONDS.sleep(2);
            this.sendLogMessage(logRecord, token, retryCount);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("线程被中断，发送操作日志到ipaass失败：{}", logRecord);
        }
    }

    /**
     * 根据重试次数计算发送次数
     * <p>
     * 该方法的目的是计算消息或请求的发送次数，基于重试次数计算发送次数的逻辑
     * 是在最大重试次数的基础上加上初始发送次数（视为第0次重试），然后减去当前的重试次数
     * 这样做是为了在不改变最大重试次数限制的前提下，正确计算出实际的发送次数
     *
     * @param retryCount 当前的重试次数，表示已经进行了多少次重试
     * @return 发送次数，即包括初始尝试在内的总尝试次数
     */
    private int getSendCount(int retryCount) {
        return MAX_RETRIES - retryCount;
    }
}
